package com.axend.aerosense.common.connect.ble;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.os.Build;
import android.os.Handler;

import com.axend.aerosense.base.app.BaseApplication;
import com.axend.aerosense.common.connect.bean.BleMessageEntity;
import com.axend.aerosense.base.utils.BleUtils;
import com.axend.aerosense.base.utils.ByteUtils;
import com.axend.aerosense.common.connect.bean.BleMsgEntity;
import com.axend.aerosense.common.connect.bean.BleNotifyEntity;
import com.axend.aerosense.common.connect.bean.DeviceConnectType;
import com.axend.aerosense.common.connect.ble.handler.BleDeviceIdHandler;
import com.axend.aerosense.common.connect.ble.handler.BleDeviceServerHandler;
import com.axend.aerosense.common.connect.ble.handler.BleDeviceWifiHandler;
import com.axend.aerosense.common.connect.ble.handler.BleResetDeviceHandler;
import com.axend.aerosense.common.connect.ble.handler.BleSetStateHandler;
import com.axend.aerosense.common.connect.impl.IBleConnect;
import com.axend.aerosense.common.connect.impl.IConnectCallback;
import com.axend.aerosense.common.connect.impl.IDeviceConnect;
import com.blankj.utilcode.util.LogUtils;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@SuppressLint("MissingPermission")
public class BleClient implements IBleConnect {

    public static final String DEVICE_BLUETOOTH_NAME = "AeroSense Wavve";  //设备蓝牙名

    //蓝牙扫描时间
    private static final long BLE_SCAN_DELAYED = 10 * 1000;
    // 单个数据包大小设置
    private static final int BLE_DATA_PACKET_SIZE = 256;
    private static final String UUID_WRITE = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
    private static final String UUID_NOTIFY = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
    //此属性一般不用修改
    private static final String BLUETOOTH_NOTIFY_ID = "00002902-0000-1000-8000-00805f9b34fb";

    private BluetoothAdapter bluetoothAdapter;
    private BluetoothGatt bluetoothGatt;
    private boolean isConnected = false;  //蓝牙连接状态
    private BluetoothAdapter.LeScanCallback leScanCallback;

    //写数据的Characteristic
    private BluetoothGattCharacteristic writeCharacteristic;
    private Handler bleScanHandler;
    private Runnable delayedRunnable;
    private IConnectCallback callback;
    private BleHandlerManager bleHandlerManager;

    private String connectBleName = DEVICE_BLUETOOTH_NAME;

    private static class Holder {
        private static final BleClient INSTANCE = new BleClient();
    }

    public static BleClient getInstance() {
        return Holder.INSTANCE;
    }

    private BleClient() {
        this.bluetoothAdapter = BleUtils.getInstance().getBluetoothAdapter();
        initBleMsgHandler();
        init();
    }

    private void init() {
        //扫描回调
        if (leScanCallback == null) {
            leScanCallback = (device, rssi, scanRecord) -> {
                //搜索指定名称蓝牙
                if (connectBleName.equalsIgnoreCase(device.getName())) {
                    stopScan();
                    connectBle(device);
                }
                LogUtils.d("当前蓝牙扫描设备：", device.getName());
            };
        }
        //扫描延时任务
        if (delayedRunnable == null) {
            delayedRunnable = () -> {
                stopScan();
                LogUtils.d("蓝牙扫描超时，未找到设备");
                callbackState(IConnectCallback.STATE_FAILED);
            };
        }
    }

    /**
     * 初始化蓝牙消息管理器
     */
    private void initBleMsgHandler() {
        bleHandlerManager = new BleHandlerManager();
        bleHandlerManager.addHandler(new BleResetDeviceHandler());
        bleHandlerManager.addHandler(new BleDeviceIdHandler());
        bleHandlerManager.addHandler(new BleDeviceWifiHandler());
        bleHandlerManager.addHandler(new BleDeviceServerHandler());
        bleHandlerManager.addHandler(new BleSetStateHandler());
    }

    /**
     * 停止扫描蓝牙
     */
    private void stopScan() {
        if (leScanCallback != null) {
            bluetoothAdapter.stopLeScan(leScanCallback);
            LogUtils.d("停止扫描");
        }
        if (bleScanHandler != null && delayedRunnable != null) {
            bleScanHandler.removeCallbacks(delayedRunnable);
        }
    }

    private void bleScan() {
        //指定扫描时长，结束扫描
        bleScanHandler = new Handler();
        bleScanHandler.postDelayed(delayedRunnable, BLE_SCAN_DELAYED);
        //开始扫描
        bluetoothAdapter.startLeScan(leScanCallback);
    }

    private void connectBle(BluetoothDevice device) {
        if (device == null) return;

        BluetoothGattCallback callback = new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    LogUtils.d("---------------------->连接成功");
                    // 开始查找服务，只有找到服务才算是真的连接上
                    bluetoothGatt.discoverServices();
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    LogUtils.d("----------------------->连接断开");
                    isConnected = false;
                    callbackState(IConnectCallback.STATE_FAILED);
                }
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    LogUtils.d("---------------------->发现服务开启");

                    List<BluetoothGattService> services = bluetoothGatt.getServices();
                    boolean isFindPropertyWrite = false;
                    boolean isFindPropertyNotify = false;
                    for (BluetoothGattService service : services) {
                        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
                        for (BluetoothGattCharacteristic characteristic : characteristics) {
                            if (characteristic.getProperties() == BluetoothGattCharacteristic.PROPERTY_WRITE) {
                                LogUtils.d("------->find write");
                                writeCharacteristic = characteristic;
                                isFindPropertyWrite = true;
                            }

                            //遍历查找服务
                            if (characteristic.getProperties() == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
                                LogUtils.d("------->find notify");
                                boolean b = enableNotification(true, characteristic);
                                isConnected = b;
                                isFindPropertyNotify = b;
                            }

                            if (isFindPropertyWrite && isFindPropertyNotify) break;
                        }
                        if (isFindPropertyWrite && isFindPropertyNotify) break;
                    }
                    //更改数据包大小
                    try {
                        Thread.sleep(600);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    gatt.requestMtu(BLE_DATA_PACKET_SIZE);
                    callbackState(isConnected ? IConnectCallback.STATE_SUCCESS : IConnectCallback.STATE_FAILED);
                }
            }

            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                LogUtils.d("写入蓝牙芯片缓冲区数据",
                        status == BluetoothGatt.GATT_SUCCESS ? "写入成功！" + "状态码：" + status : "写入失败！" + "状态码：" + status);
            }

            @Override
            public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
                super.onMtuChanged(gatt, mtu, status);
                if (BluetoothGatt.GATT_SUCCESS == status) {
                    LogUtils.d("数据包大小更改成功，当前大小：", mtu);

                } else {
                    LogUtils.d("数据包大小更改失败，当前大小：", mtu);
                    callbackState(IConnectCallback.STATE_FAILED);
                }
            }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                byte[] value = characteristic.getValue();
                LogUtils.d("通知数据:" + ByteUtils.bytesToHexString(value), "数据长度：", value.length);
                //parseData(value);
                handlerBleMsg(value);
            }
        };
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            bluetoothGatt = device.connectGatt(BaseApplication.getInstance().getApplicationContext(), false, callback, BluetoothDevice.TRANSPORT_LE);
        else
            bluetoothGatt = device.connectGatt(BaseApplication.getInstance().getApplicationContext(), false, callback);


    }

    /**
     * 处理蓝牙通知的数据
     */
    private void handlerBleMsg(byte[] data) {
        BleNotifyEntity notifyEntity = bleHandlerManager.decode(data);
        if (null == notifyEntity) {
            return;
        }
        callbackReadDate(notifyEntity);
    }

    private void parseData(byte[] value) {
        if (null == value || value.length == 0) return;
        try {
            if ((value.length == 29 || value.length == 20) && value[0] == 0x13) {
                ByteBuffer buffer = ByteBuffer.wrap(value);
                //跳过前面消息头
                buffer.position(14);
                byte[] tag = getBytes(2, buffer);
                if (null != tag && tag[0] == 0x04 && tag[1] == 0x10) {
                    BleMessageEntity bleMessageEntity = new BleMessageEntity();
                    bleMessageEntity.setTag(BleMessageEntity.Tag.getDeviceUUID);
//                    bleMessageEntity.setDeviceId(getBytes(13, buffer));
                    bleMessageEntity.setDeviceId(getBytes(buffer.remaining(), buffer));
                    callbackReadDate(bleMessageEntity);
                    return;
                } else if (null != tag && tag[0] == 0x04 && tag[1] == 0x11) {
                    BleMessageEntity bleMessageEntity = new BleMessageEntity();
                    bleMessageEntity.setTag(BleMessageEntity.Tag.resetDevice);
                    bleMessageEntity.setState(value[value.length - 1] == BleMessageEntity.State.setSuccess.getByte() ? BleMessageEntity.State.setSuccess : BleMessageEntity.State.setFail);
                    callbackReadDate(bleMessageEntity);
                }
            } else if (value.length == 1) {
                //设置状态数据
                callbackReadDate(new BleMessageEntity(value[0] == BleMessageEntity.State.setSuccess.getByte() ? BleMessageEntity.State.setSuccess : null));
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private byte[] getBytes(int length, ByteBuffer buffer) {
        if (null == buffer || !buffer.hasRemaining()) return null;

        byte[] b = new byte[length];
        buffer.get(b);
        return b;
    }

    private void writeData(byte[] data) {
        if (data == null || writeCharacteristic == null || bluetoothGatt == null)
            return;
        writeCharacteristic.setValue(data);
        bluetoothGatt.writeCharacteristic(writeCharacteristic);
        LogUtils.d("写入数据", ByteUtils.bytesToHexString(data));
    }

    /**
     * 设置通知
     * 注意：此处UUID区分大小写
     *
     * @param enable         true为开启false为关闭
     * @param characteristic 通知特征
     * @return
     */
    private boolean enableNotification(boolean enable, BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null || characteristic == null)
            return false;
        if (!bluetoothGatt.setCharacteristicNotification(characteristic, enable))
            return false;
        BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString(BLUETOOTH_NOTIFY_ID));
        if (clientConfig == null)
            return false;

        if (enable) {
            clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else {
            clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        return bluetoothGatt.writeDescriptor(clientConfig);
    }

    private void callbackState(int state) {
        if (callback == null) return;
        callback.state(state);
        if (state == IConnectCallback.STATE_FAILED) close();
    }

    private void callbackReadDate(Object data) {
        if (callback == null) return;
        callback.readData(data);
    }

    /* ------------------------------------------------------------------------------------ */

    /**
     * 是否连接
     *
     * @return
     */
    @Override
    public boolean isConnect() {
        return isConnected;
    }

    /**
     * 断开连接
     */
    @Override
    public void close() {
        if (bluetoothAdapter != null && bluetoothGatt != null) {
            refreshGattCache(bluetoothGatt);
            bluetoothGatt.disconnect();
            bluetoothGatt.close();
            bluetoothGatt = null;
        }
    }

    /**
     * 刷新gatt缓存
     * @param gatt
     * @return
     */
    public boolean refreshGattCache(BluetoothGatt gatt) {
        boolean result = false;
        try {
            if (gatt != null) {
                Method refresh = BluetoothGatt.class.getMethod("refresh");
                if (refresh != null) {
                    refresh.setAccessible(true);
                    result = (boolean) refresh.invoke(gatt, new Object[0]);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 扫描并连接设备
     */
    @Override
    public void connect() {
        bleScan();
    }

    /**
     * 发送数据
     *
     * @param data
     */
    @Override
    public void sendMsg(Object data) {
        if (data instanceof BleMessageEntity) {
            byte[] message = ((BleMessageEntity) data).getMessage();
            writeData(message);
        } else if (data instanceof byte[]) {
            writeData((byte[]) data);
        } else if (data instanceof BleMsgEntity) {
            byte[] writeBleMsg = bleHandlerManager.getWriteBleMsg((BleMsgEntity) data);
            writeData(writeBleMsg);
        }
    }

    @Override
    public void setCallback(IConnectCallback callback) {
        this.callback = callback;
    }

    @Override
    public DeviceConnectType getConnectType() {
        return DeviceConnectType.TYPE_BLE;
    }

    @Override
    public void setDeviceName(String name) {
        this.connectBleName = name;
    }

    public interface BleCallback {
        void state(int state);

        void readData(Object data);
    }

}
